// Copyright (c) Lawrence Livermore National Security, LLC and other VisIt
// Project developers.  See the top-level LICENSE file for dates and other
// details.  No copyright assignment is required to contribute to VisIt.

// ************************************************************************* //
//                              RuntimeSetting.C                             //
// ************************************************************************* //
#include <cstring>
#include <map>
#include <string>

#include <RuntimeSetting.h>
#include <DebugStream.h>
#include <Environment.h>
#include <InstallationFunctions.h>
#include <VisItException.h>

// Anonymous namespace
namespace {

struct Setting
{
    const char *key;           ///< key for identifying this setting in code.
    const char *default_value; ///< compile-time default for the setting
    bool        isFile;
    const char *cmdline;       ///< command line option
    const char *envvar;        ///< which environment variable affects this?
    const char *helpstr;       ///< short help string which describes what this does
    /// @todo const char* cfg; ///< for the key which sets this in the cfg file
};

// This essentially creates a unique namespace essentially holding two `arrays'
// which give information on a setting.  The first gives basic, compile-time information:
// default values and how to influence this particular setting.  The second
// holds runtime overrides for variables, set for example when we parse command
// line options.
struct s
{
    static const struct Setting settings[];

    typedef std::map<std::string, std::string> runtime_type;
    static runtime_type runtime_settings;
};

#ifdef __APPLE__
  static const char lib_osmesa[] = "./lib/libOSMesa.dylib";
  static const char lib_gl[] =
    "/System/Library/Frameworks/OpenGL.framework/Versions/Current/OpenGL";
#elif defined(_WIN32)
  static const char lib_osmesa[] = "osmesa32.dll";
  static const char lib_gl[] = "opengl32.dll";
#elif defined(_AIX)
  static const char lib_osmesa[] = "./lib/libOSMesa.a(shr_64.o);./lib/libOSMesa.a(shr.o)";
  static const char lib_gl[] = "libGL.a(shr_64.o);libGL.a(shr.o)";
#else
  static const char lib_osmesa[] = "./lib/libOSMesa.so";
  static const char lib_gl[] = "libGL.so.1;libGL.so";
#endif

// Keep this ordered by key (first field per entry).
// Strings are special: if they start with ".", they're assumed to be a
// relative path, and get our installation directory prepended.
const struct Setting s::settings[] = {
    {"mesa-lib", lib_osmesa, true, "--mesa-lib", "VISIT_MESA_LIB",
     "Library with mangled offscreen mesa symbols."},
    {"system-gl", lib_gl, true, "--system-gl-lib", "VISIT_GL_LIB",
     "Library which implements OpenGL."},
    {"x-args", "", false, "-x-args", "VISIT_X_ARGS",
     "Arguments to pass to X servers that VisIt starts up."},
    {"x-display", "", false, "-display", "VISIT_DISPLAY",
     "Sets the display variable that VisIt will use when launching X servers."},
    {"threads", "0", false, "-threads", "VISIT_THREADS",
     "Sets the number of threads VisIt will use."}
};

// Runtime modification map.  This is where we'll put settings we figure out
// at runtime; currently that means those obtained via parsing command lines.
s::runtime_type s::runtime_settings;

// ****************************************************************************
//  Function: RuntimeSetting::compile_time_default
//
//  Purpose: Looks up the compile time default for the given type.
//
//  Programmer: Tom Fogal
//  Creation: July 22, 2009
//
//  Modifications:
//    Eric Brugger, Tue Mar  2 09:08:43 PST 2010
//    I modified the logic that prepends the arch dir to handle the case of
//    multiple paths seperated by a semicolon, which occurs on AIX.
//
// ****************************************************************************

static std::string compile_time_default(int i)
{
    std::string retval;

    // If the setting starts with ".", assume it's a relative path and
    // prepend our arch dir.
    if(s::settings[i].isFile)
    {
        char *default_str = new char[strlen(s::settings[i].default_value)+1];
        strcpy(default_str, s::settings[i].default_value);
        char *str = default_str;
        char *str2 = strchr(str, ';');
        if (str2 != NULL) str2[0] = '\0';
        if (str[0] == '.')
            retval = GetVisItArchitectureDirectory() + std::string(str + 1);
        else
            retval = std::string(str);
        while (str2 != NULL)
        {
            str = str2 + 1;
            if (str[0] == '.')
                retval = retval + ";" + GetVisItArchitectureDirectory() +
                    std::string(str + 1);
            else
                retval = retval + ";" + std::string(str);
            str2 = strchr(str, ';');
            if (str2 != NULL) str2[0] = '\0';
        }
        delete [] default_str;
    }
    else
        retval = s::settings[i].default_value;

    return retval;
}

// ****************************************************************************
//  Function: RuntimeSetting::find_setting
//
//  Purpose: searches through the settings list for the entry with the given
//           key.  Looks for overrides in environment variables and parsed
//           command line options.
//
//  Programmer: Tom Fogal
//  Creation: June 27, 2009
//
//  Modifications:
//    Brad Whitlock, Nov 13, 2009
//    I detemplated the code to simplify it and make it work on AIX.
//    
// ****************************************************************************

static bool
find_setting(const char *key, std::string &retval)
{
    size_t i;
    size_t len = strlen(key);
    const size_t n = sizeof(s::settings) / sizeof(struct Setting);

    // lookup the default from our settings array.
    for(i=0; i < n; ++i)
    {
        if(strncmp(s::settings[i].key, key, len) == 0)
        {
            retval = compile_time_default((int)i);
            break;
        }
    }
    if(i == n)
    {
        return false;
    }

    /// @todo FIXME Here we *should* lookup the value in a configuration file.

    // Now lookup from the environment variable, if it exists.
    if(Environment::exists(s::settings[i].envvar))
    {
        retval = Environment::get(s::settings[i].envvar);
    }

    // Finally, check any overrides from cmd line options.
    s::runtime_type::const_iterator iter =
      s::runtime_settings.find(std::string(key));
    if(iter != s::runtime_settings.end())
    {
        retval = iter->second;
    }

    return true;
}

// ****************************************************************************
// Function: RuntimeSetting::create_clo_overrides
//
// Purpose: Create overrides from a given set of command line options (clo's)
//
//  Programmer: Tom Fogal
//  Creation: June 27, 2009
//
//  Modifications:
//    Brad Whitlock, Nov 13, 2009
//    I detemplated the code to simplify it and make it work on AIX.
//
// ****************************************************************************
static void
create_clo_overrides(int argc, const char *argv[])
{
    const size_t n = sizeof(s::settings) / sizeof(struct Setting);
    for(int i=0; i < argc; ++i)
    {
        for(size_t j=0; j < n; ++j)
        {
            if(strcmp(s::settings[j].cmdline, argv[i]) == 0)
            {
                if(i+1 >= argc)
                {
                    debug1 << argv[i] << " option given with no parameter!"
                           << "  Ignoring option." << std::endl;
                    return;
                }

                debug1 << "Storing override " << argv[i] << " = " << argv[i+1]
                       << endl;
                std::string override(argv[i+1]);
                s::runtime_settings[s::settings[j].key] = override;

                ++i;
                break;
            }
        }
    }
}

} // end anon namespace.

namespace RuntimeSetting {

// ****************************************************************************
//  Function: RuntimeSetting::lookup
//
//  Purpose:  Looks up a value.
//
//  Programmer: Tom Fogal
//  Creation: June 27, 2009
//
// ****************************************************************************
int
lookupi(const char *key)
{
    int retval = 0;
    std::string value;
    if(find_setting(key, value))
    {
        std::istringstream read_value(value);
        read_value >> retval;
    }
    return retval;
}

double
lookupf(const char *key)
{
    double retval = 0.;
    std::string value;
    if(find_setting(key, value))
    {
        std::istringstream read_value(value);
        read_value >> retval;
    }
    return retval;
}

// Modifications:
//
//   Tom Fogal, Wed May 26 11:00:44 MDT 2010
//   Fix parsing -- we should get the whole string!
std::string
lookups(const char *key)
{
    std::string value;
    find_setting(key, value);
    return value;
}

bool
lookupb(const char *key)
{
    bool retval = true;
    std::string value;
    if(find_setting(key, value))
    {
        std::istringstream read_value(value);
        read_value >> retval;
    }
    return retval;
}

// ****************************************************************************
//  Function: RuntimeSetting::parse_command_line
//
//  Purpose:  Creates overrides based on command line parameters.
//
//  Programmer: Tom Fogal
//  Creation: June 27, 2009
//
// ****************************************************************************
void parse_command_line(int argc, const char *argv[])
{
    create_clo_overrides(argc, argv);
}

}  /* namespace RuntimeSetting */
